package com.byd.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.json.JSONObject;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import com.byd.application.MyApplication;
import com.byd.utils.BitmapUtils;
import com.byd.utils.Lg;

public final class DataBase extends SQLiteOpenHelper {
	private static final String[] strDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
			"e", "f"};
	private static final MessageDigest sDigest;
	private static DataBase sDataBase;
	static {
		MessageDigest digest = null;
		try {
			digest = MessageDigest.getInstance("MD5");
		}
		catch (final NoSuchAlgorithmException e) {
			Lg.printStackTrace(e);
		}
		sDigest = digest;
	}
	private static final String SQL_NAME = "BydSystemUpdate";
	private static final String TABLE_CONFIG = "table_config";
	private static final String CONFIG_KEY = "config_key";
	private static final String CONFIG_VALUE = "config_value";
	private static final String[] CONFIG_RESULT = new String[]{CONFIG_KEY, CONFIG_VALUE};
	private SQLiteDatabase mDatabase;
	public static final String CACHE_DATA = "data";
	public static final String CACHE_PIC = "pic";
	public static final String CACHE_FILE = "file";

	private static abstract class DataRunnable<T> implements Runnable {
		String key;
		T value;
		Object tag;

		DataRunnable(String key, T value) {
			this(key, value, null);
		}

		DataRunnable(String key, T value, Object tag) {
			this.key = key;
			this.value = value;
			this.tag = tag;
		}
	}

	public synchronized static void init() {
		if(sDataBase == null) {
			PackageManager manager = MyApplication.getInstance().getPackageManager();
			int version;
			try {
				PackageInfo info = manager.getPackageInfo(MyApplication.getInstance().getPackageName(), 0);
				version = info.versionCode;
			}
			catch (Exception e) {
				version = (int) (Math.random() * (Integer.MAX_VALUE - 1000)) + 1;
				Lg.printStackTrace(e);
			}
			sDataBase = new DataBase(MyApplication.getInstance(), version);
		}
	}

	public DataBase(Context context, int version) {
		super(context, SQL_NAME, null, version);
		Lg.d("version: " + version);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("create table if not exists " + TABLE_CONFIG + " (" + CONFIG_KEY + " varchar primary key, "
				+ CONFIG_VALUE + " varchar) ");
		mDatabase = db;
		Lg.d("onCreate");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		Lg.i("onUpgrade: " + oldVersion + " --> " + newVersion);
		mDatabase = db;
		clearSql(db);
	}

	@Override
	public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		Lg.e("onDowngrade: " + oldVersion + " --> " + newVersion);
		mDatabase = db;
		clearSql(db);
	}

	private void clearSql(SQLiteDatabase db) {
		try {
			Lg.e("clear sql");
			db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONFIG);
			sDataBase.onCreate(db);
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
	}

	private SQLiteDatabase getDatabase() {
		try {
			final SQLiteDatabase database = getWritableDatabase();
			if(database != null) {
				mDatabase = database;
			}
		}
		catch (final Exception e) {
			Lg.w("no valid WritableDatabase");
		}
		return mDatabase;
	}

	public synchronized static void set(String key, JSONObject obj) {
		set(key, obj.toString());
	}

	public static void setAsync(String key, JSONObject obj) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<JSONObject>(key, obj) {
			@Override
			public void run() {
				set(key, value);
			}
		});
	}

	public synchronized static void set(String key, int value) {
		set(key, Integer.toString(value));
	}

	public static void setAsync(String key, int value) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<Integer>(key, value) {
			@Override
			public void run() {
				set(key, value);
			}
		});
	}

	public static boolean set(String key, String value) {
		final SQLiteDatabase db = sDataBase.getDatabase();
		final ContentValues values = new ContentValues();
		values.put(CONFIG_KEY, key);
		values.put(CONFIG_VALUE, value);
		Cursor c = null;
		try {
			c = db.query(TABLE_CONFIG, CONFIG_RESULT, CONFIG_KEY + "=?", new String[]{key}, null, null, null);
			if(c.getCount() > 0) {
				db.update(TABLE_CONFIG, values, CONFIG_KEY + "=?", new String[]{key});
			}
			else {
				db.insert(TABLE_CONFIG, null, values);
			}
			return true;
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
			return false;
		}
		finally {
			if(c != null) {
				c.close();
			}
			values.clear();
		}
	}

	public static void setAsync(String key, String value) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<String>(key, value) {
			@Override
			public void run() {
				set(key, value);
			}
		});
	}

	public static String getString(String key) {
		final SQLiteDatabase db = sDataBase.getDatabase();
		Cursor c = null;
		String value = null;
		try {
			c = db.query(TABLE_CONFIG, CONFIG_RESULT, CONFIG_KEY + "=?", new String[]{key}, null, null, null);
			if(c.getCount() > 0) {
				c.moveToFirst();
				value = c.getString(1);
			}
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		finally {
			if(c != null) {
				c.close();
			}
		}
		return value;
	}

	public synchronized static JSONObject getJSONObject(String key) {
		try {
			return new JSONObject(getString(key));
		}
		catch (final Exception e) {
			return null;
		}
	}

	public synchronized static int getInt(String key, int defaultValue) {
		try {
			return Integer.parseInt(getString(key));
		}
		catch (final Exception e) {
			return defaultValue;
		}
	}

	public synchronized static boolean delete(String key) {
		final SQLiteDatabase db = sDataBase.getDatabase();
		Cursor c = null;
		try {
			c = db.query(TABLE_CONFIG, CONFIG_RESULT, CONFIG_KEY + "=?", new String[]{key}, null, null, null);
			if(c.getCount() > 0) {
				db.delete(TABLE_CONFIG, CONFIG_KEY + "=?", new String[]{key});
				Lg.d("delete: " + key + " success");
				return true;
			}
			return true;
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		finally {
			if(c != null) {
				c.close();
			}
		}
		Lg.w("delete: " + key + " failed");
		return false;
	}

	public static void deleteAsync(String key) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<Integer>(key, 0) {
			@Override
			public void run() {
				delete(key);
			}
		});
	}

	private static String byteToArrayString(byte b) {
		int i = b;
		if(i < 0) {
			i += 256;
		}
		final int d1 = i / 16;
		final int d2 = i % 16;
		return strDigits[d1] + strDigits[d2];
	}

	public static String byteToHexString(byte[] data) {
		final StringBuilder builder = new StringBuilder();
		for (int i = 0; i < data.length; i++) {
			builder.append(byteToArrayString(data[i]));
		}
		return builder.toString();
	}

	public synchronized static String getMD5(String data) {
		String msg = null;
		try {
			final byte[] bytes = data.getBytes();
			sDigest.reset();
			final byte[] digest = sDigest.digest(bytes);
			msg = byteToHexString(digest);
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		return msg;
	}

	public synchronized static void cache(String key, int value) {
		cache(key, Integer.toString(value));
	}

	public static void cacheAsync(String key, int value) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<Integer>(key, value) {
			@Override
			public void run() {
				cache(key, value);
			}
		});
	}

	public synchronized static void cache(String key, JSONObject obj) {
		cache(key, obj.toString());
	}

	public static void cacheAsync(String key, JSONObject value) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<JSONObject>(key, value) {
			@Override
			public void run() {
				cache(key, value);
			}
		});
	}

	public synchronized static void cache(String key, String value) {
		cache(CACHE_DATA, key, value.getBytes());
	}

	public static void cacheAsync(String key, String value) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<String>(key, value) {
			@Override
			public void run() {
				cache(key, value);
			}
		});
	}

	public synchronized static void cache(String type, String key, byte[] data) {
		FileOutputStream stream = null;
		try {
			final File file = new File(checkPath(type), getMD5(key));
			stream = new FileOutputStream(file);
			stream.write(data);
			stream.flush();
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		finally {
			try {
				if(stream != null) {
					stream.close();
				}
			}
			catch (final Exception e) {
				Lg.printStackTrace(e);
			}
		}
	}

	public static void cacheAsync(String type, String key, byte[] data) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<byte[]>(key, data, type) {
			@Override
			public void run() {
				cache((String) tag, key, value);
			}
		});
	}

	public synchronized static void cache(String key, Bitmap bmp) {
		FileOutputStream stream = null;
		try {
			File file = new File(checkPath(CACHE_PIC), getMD5(key));
			stream = new FileOutputStream(file);
			bmp.compress(CompressFormat.PNG, 80, stream);
			stream.flush();
			stream.close();
			stream = null;
			file = null;
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		finally {
			try {
				if(stream != null) {
					stream.flush();
					stream.close();
				}
			}
			catch (final Exception e) {
				Lg.printStackTrace(e);
			}
		}
	}

	public static void cacheAsync(String key, Bitmap bmp) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<Bitmap>(key, bmp) {
			@Override
			public void run() {
				cache(key, value);
			}
		});
	}

	private static File checkPath(String type) {
		File dir = new File(Path.CACHE);
		if(!dir.exists()) {
			dir.mkdir();
		}
		dir = new File(dir, type);
		if(!dir.exists()) {
			dir.mkdir();
		}
		return dir;
	}

	public synchronized static int getCacheInt(String key, int defaultValue) {
		final String value = getCacheString(key);
		try {
			return Integer.parseInt(value);
		}
		catch (final Exception e) {
			return defaultValue;
		}
	}

	public synchronized static JSONObject getCacheJSONObject(String key) {
		try {
			return new JSONObject(getCacheString(key));
		}
		catch (final Exception e) {
			return null;
		}
	}

	public synchronized static String getCacheString(String key) {
		final byte[] bytes = getCacheBytes(key, CACHE_DATA);
		if(bytes == null) {
			return null;
		}
		return new String(bytes);
	}

	public synchronized static byte[] getCacheBytes(String key, String type) {
		final File file = new File(Path.CACHE + File.separator + type, getMD5(key));
		if(file.exists()) {
			FileInputStream stream = null;
			try {
				stream = new FileInputStream(file);
				final byte[] bytes = new byte[(int) file.length()];
				stream.read(bytes);
				return bytes;
			}
			catch (final Exception e) {
				Lg.printStackTrace(e);
			}
			finally {
				try {
					if(stream != null) {
						stream.close();
					}
				}
				catch (final Exception e) {
					Lg.printStackTrace(e);
				}
			}
		}
		return null;
	}

	public synchronized static Bitmap getCacheBitmap(String key) {
		final File file = new File(Path.CACHE + File.separator + CACHE_PIC, getMD5(key));
		if(file.exists()) {
			FileInputStream stream = null;
			try {
				final byte[] data = new byte[(int) file.length()];
				stream = new FileInputStream(file);
				stream.read(data);
				final Bitmap bmp = BitmapUtils.getBitmap(data);
				return bmp;
			}
			catch (final Exception e) {
				Lg.printStackTrace(e);
			}
			finally {
				if(stream != null) {
					try {
						stream.close();
					}
					catch (final Exception ex) {
						Lg.printStackTrace(ex);
					}
				}
			}
		}
		return null;
	}

	public synchronized static boolean deleteCache(String key) {
		final File file = new File(Path.CACHE, getMD5(key));
		if(file.exists()) {
			if(file.delete()) {
				return true;
			}
			Lg.w("delete cache: " + key + " failed");
		}
		return true;
	}

	public static void deleteCacheAsync(String key) {
		MyThreadPool.getIOExecutors().execute(new DataRunnable<Integer>(key, 0) {
			@Override
			public void run() {
				deleteCache(key);
			}
		});
	}

	@Override
	protected void finalize() throws Throwable {
		Lg.i("finalize: " + toString());
		try {
			close();
		}
		catch (final Exception e) {
			Lg.printStackTrace(e);
		}
		super.finalize();
	}
}
